import json
import base64
import datetime

from .timer import format_interval


def shorten_text(text: str, width: int):
    """
    >>> shorten_text(None, width=60) is None
    True
    >>> expect = 'x' * 60 + '..'
    >>> shorten_text('x' * 80, width=60) == expect
    True
    """
    if text is None:
        return None
    return (text[:width] + '..') if len(text) > width else text


class TinyopsJSONEncoder(json.JSONEncoder):

    def default(self, obj):
        if isinstance(obj, (datetime.date, datetime.time, datetime.datetime)):
            if obj.tzinfo is None:
                obj = obj.replace(tzinfo=datetime.timezone.utc)
            obj = obj.astimezone(datetime.timezone.utc)
            return obj.strftime("%Y-%m-%dT%H:%M:%SZ")
        elif isinstance(obj, datetime.timedelta):
            s = int(obj.total_seconds())
            return format_interval(s)
        else:
            return json.JSONEncoder.default(self, obj)


class JsonHelper:

    @staticmethod
    def dumps(obj, ensure_ascii=False, indent=None, **kwargs):
        """
        >>> print(JsonHelper.dumps({"hello": "你好"}, indent=4))
        {
            "hello": "你好"
        }
        >>> date = datetime.datetime(2020, 3, 2)
        >>> print(JsonHelper.dumps({"hello": date}))
        {"hello": "2020-03-02T00:00:00Z"}
        >>> delta = datetime.timedelta(seconds=10)
        >>> print(JsonHelper.dumps({"hello": delta}))
        {"hello": "10s"}
        """
        return json.dumps(
            obj, indent=indent, ensure_ascii=ensure_ascii,
            cls=TinyopsJSONEncoder, **kwargs)

    @staticmethod
    def dump(obj, fp, ensure_ascii=False, indent=None, **kwargs):
        """
        >>> import io
        >>> fp = io.StringIO()
        >>> JsonHelper.dump({"hello": "你好"}, fp, indent=4)
        >>> print(fp.getvalue())
        {
            "hello": "你好"
        }
        """
        json.dump(
            obj, fp, indent=indent, ensure_ascii=ensure_ascii,
            cls=TinyopsJSONEncoder, **kwargs)

    loads = json.loads
    load = json.load

    @staticmethod
    def pretty(value, shorten=False):
        """
        >>> print(JsonHelper.pretty({"hello": "你好"}))
        {
            "hello": "你好"
        }
        >>> text = JsonHelper.pretty({"hello": ["你好" * 1000]}, shorten=True)
        >>> len(text) < 120
        True
        """
        width = 60
        if not isinstance(shorten, bool) and isinstance(shorten, int):
            width = shorten

        def shorten_value(x):
            if isinstance(x, str):
                return shorten_text(x, width)
            if isinstance(x, dict):
                return {k: shorten_value(v) for k, v in x.items()}
            if isinstance(x, list):
                return [shorten_value(v) for v in x]
            return x

        if shorten:
            value = shorten_value(value)
        return JsonHelper.dumps(value, indent=4)

    @staticmethod
    def encode_base64(obj) -> str:
        """
        >>> assert JsonHelper.encode_base64({"hello": "你好"})
        """
        json_data = JsonHelper.dumps(obj).encode('utf-8')
        return base64.urlsafe_b64encode(json_data).decode('ascii')

    @staticmethod
    def decode_base64(data: str) -> dict:
        """
        >>> value = JsonHelper.encode_base64({"hello": "你好"})
        >>> expect = {"hello": "你好"}
        >>> JsonHelper.decode_base64(value) == expect
        True
        """
        json_data = base64.urlsafe_b64decode(data.encode('ascii')).decode('utf-8')
        return JsonHelper.loads(json_data)
